-- -------------------------------------------------------------------------
-- UI/input.lua
-- Input Handling System
-- -------------------------------------------------------------------------

local input_module = {}

local state = require("ClickUl.UI.state")

-- [Lua 5.4] <const> for function references
local table_insert <const> = table.insert
local ipairs <const> = ipairs
local tostring <const> = tostring

-- [Lua 5.4] <const> for static control list
local TEXTINPUT_BLOCK_CONTROLS <const> = {
    30, 31, 32, 33, 34, 35, -- Left/Right/Forward/Back/MoveUpOnly/MoveDownOnly
    21, 22, -- Sprint/Jump
    36, -- Stealth Mode
    44, 45, 46, 47, -- Cover/Reload/Duck/Melee
    75, 76, -- Exit Vehicle/VehicleExit
    86, -- VehicleHorn
    23, 24, 25,
    -- [NEW] Block P, C, V, B, M, G keys during text input
    27, -- INPUT_PHONE (P)
    26, -- INPUT_LOOK_BEHIND (C)
    0,  -- INPUT_NEXT_CAMERA (V)
    244, -- INPUT_INTERACTION_MENU (B)
    199, -- INPUT_FRONTEND_PAUSE_ALTERNATE (M)
    58,  -- INPUT_THROW_GRENADE (G)
}

local MERGED_CONTROLS = nil
local MERGED_CONTROLS_LEN = 0

local function init_merged_controls()
    if MERGED_CONTROLS then return end

    local seen = {}
    MERGED_CONTROLS = {}

    -- Add base controls
    local base = state.BLOCK_CONTROLS
    if base then
        for i = 1, #base do
            local ctrl = base[i]
            if not seen[ctrl] then
                seen[ctrl] = true
                MERGED_CONTROLS[#MERGED_CONTROLS + 1] = ctrl
            end
        end
    end

    -- Add text input controls
    for i = 1, #TEXTINPUT_BLOCK_CONTROLS do
        local ctrl = TEXTINPUT_BLOCK_CONTROLS[i]
        if not seen[ctrl] then
            seen[ctrl] = true
            MERGED_CONTROLS[#MERGED_CONTROLS + 1] = ctrl
        end
    end

    MERGED_CONTROLS_LEN = #MERGED_CONTROLS
end

-- [Virtual Key Name Mapping]
local VK_NAMES = {
    -- Mouse buttons
    [0x01] = "LMB",
    [0x02] = "RMB",
    [0x04] = "MMB",
    [0x05] = "X1",
    [0x06] = "X2",

    -- Special keys
    [0x08] = "Backspace",
    [0x09] = "Tab",
    [0x0D] = "Enter",
    [0x10] = "Shift",
    [0x11] = "Ctrl",
    [0x12] = "Alt",
    [0x13] = "Pause",
    [0x14] = "CapsLock",
    [0x1B] = "Esc",
    [0x20] = "Space",
    [0x21] = "PageUp",
    [0x22] = "PageDown",
    [0x23] = "End",
    [0x24] = "Home",
    [0x25] = "Left",
    [0x26] = "Up",
    [0x27] = "Right",
    [0x28] = "Down",
    [0x2C] = "PrintScreen",
    [0x2D] = "Insert",
    [0x2E] = "Delete",

    -- Numbers 0-9
    [0x30] = "0", [0x31] = "1", [0x32] = "2", [0x33] = "3", [0x34] = "4",
    [0x35] = "5", [0x36] = "6", [0x37] = "7", [0x38] = "8", [0x39] = "9",

    -- Letters A-Z
    [0x41] = "A", [0x42] = "B", [0x43] = "C", [0x44] = "D", [0x45] = "E",
    [0x46] = "F", [0x47] = "G", [0x48] = "H", [0x49] = "I", [0x4A] = "J",
    [0x4B] = "K", [0x4C] = "L", [0x4D] = "M", [0x4E] = "N", [0x4F] = "O",
    [0x50] = "P", [0x51] = "Q", [0x52] = "R", [0x53] = "S", [0x54] = "T",
    [0x55] = "U", [0x56] = "V", [0x57] = "W", [0x58] = "X", [0x59] = "Y",
    [0x5A] = "Z",

    -- Numpad
    [0x60] = "Num0", [0x61] = "Num1", [0x62] = "Num2", [0x63] = "Num3",
    [0x64] = "Num4", [0x65] = "Num5", [0x66] = "Num6", [0x67] = "Num7",
    [0x68] = "Num8", [0x69] = "Num9",
    [0x6A] = "Num*", [0x6B] = "Num+", [0x6C] = "NumEnter",
    [0x6D] = "Num-", [0x6E] = "Num.", [0x6F] = "Num/",

    -- Function keys F1-F24
    [0x70] = "F1",  [0x71] = "F2",  [0x72] = "F3",  [0x73] = "F4",
    [0x74] = "F5",  [0x75] = "F6",  [0x76] = "F7",  [0x77] = "F8",
    [0x78] = "F9",  [0x79] = "F10", [0x7A] = "F11", [0x7B] = "F12",
    [0x7C] = "F13", [0x7D] = "F14", [0x7E] = "F15", [0x7F] = "F16",
    [0x80] = "F17", [0x81] = "F18", [0x82] = "F19", [0x83] = "F20",
    [0x84] = "F21", [0x85] = "F22", [0x86] = "F23", [0x87] = "F24",

    -- Lock keys
    [0x90] = "NumLock",
    [0x91] = "ScrollLock",

    -- Modifier keys (Left/Right variants)
    [0xA0] = "LShift", [0xA1] = "RShift",
    [0xA2] = "LCtrl",  [0xA3] = "RCtrl",
    [0xA4] = "LAlt",   [0xA5] = "RAlt",

    -- Browser keys
    [0xA6] = "BrowserBack",
    [0xA7] = "BrowserForward",
    [0xA8] = "BrowserRefresh",
    [0xA9] = "BrowserStop",
    [0xAA] = "BrowserSearch",
    [0xAB] = "BrowserFavorites",
    [0xAC] = "BrowserHome",

    -- Media keys
    [0xAD] = "VolMute",
    [0xAE] = "VolDown",
    [0xAF] = "VolUp",
    [0xB0] = "MediaNext",
    [0xB1] = "MediaPrev",
    [0xB2] = "MediaStop",
    [0xB3] = "MediaPlay",

    -- OEM keys (punctuation)
    [0xBA] = ";",  -- OEM_1
    [0xBB] = "=",  -- OEM_PLUS
    [0xBC] = ",",  -- OEM_COMMA
    [0xBD] = "-",  -- OEM_MINUS
    [0xBE] = ".",  -- OEM_PERIOD
    [0xBF] = "/",  -- OEM_2
    [0xC0] = "`",  -- OEM_3
    [0xDB] = "[",  -- OEM_4
    [0xDC] = "\\", -- OEM_5
    [0xDD] = "]",  -- OEM_6
    [0xDE] = "'",  -- OEM_7
}

--- Get the human-readable name for a VK code
--- @param vk_code number The virtual key code
--- @return string The key name (e.g., "F5", "A", "Ctrl")
function input_module.get_key_name(vk_code)
    if not vk_code or vk_code == 0 then
        return "None"
    end
    return VK_NAMES[vk_code] or ("VK_" .. tostring(vk_code))
end

--- Get all VK names table (for iteration/lookup)
--- @return table The VK_NAMES table
function input_module.get_vk_names()
    return VK_NAMES
end

--- Check if a VK code is a modifier key (Ctrl, Shift, Alt)
--- @param vk_code number The virtual key code
--- @return boolean True if modifier key
function input_module.is_modifier_key(vk_code)
    return vk_code == 0x10 or vk_code == 0x11 or vk_code == 0x12  -- Shift, Ctrl, Alt
        or vk_code == 0xA0 or vk_code == 0xA1  -- LShift, RShift
        or vk_code == 0xA2 or vk_code == 0xA3  -- LCtrl, RCtrl
        or vk_code == 0xA4 or vk_code == 0xA5  -- LAlt, RAlt
end

--- Format a keybind with modifiers (e.g., "Ctrl+Shift+F5")
--- @param vk_code number The main key code
--- @param ctrl boolean Ctrl modifier
--- @param shift boolean Shift modifier
--- @param alt boolean Alt modifier
--- @return string Formatted keybind string
function input_module.format_keybind(vk_code, ctrl, shift, alt)
    if not vk_code or vk_code == 0 then
        return "None"
    end

    local parts = {}
    if ctrl then parts[#parts + 1] = "Ctrl" end
    if shift then parts[#parts + 1] = "Shift" end
    if alt then parts[#parts + 1] = "Alt" end
    parts[#parts + 1] = input_module.get_key_name(vk_code)

    return table.concat(parts, "+")
end

-- [Virtual Key Mapping]
-- List version for iteration
local VK_LIST = {}

-- Lookup version for direct checks
local KEY_LOOKUP = {}

local function register_key(code, char, shift)
    local data = { code = code, char = char, shift = shift }
    table_insert(VK_LIST, data)
    KEY_LOOKUP[code] = data
end

-- Map A-Z (0x41 - 0x5A)
for i = 0x41, 0x5A do
    local char = string.char(i)
    register_key(i, char:lower(), char:upper())
end

-- Map 0-9 (0x30 - 0x39) with Shift symbols
local number_shift_map = {
    [0x30] = ')', [0x31] = '!', [0x32] = '@', [0x33] = '#', [0x34] = '$',
    [0x35] = '%', [0x36] = '^', [0x37] = '&', [0x38] = '*', [0x39] = '('
}
for i = 0x30, 0x39 do
    local num_char = string.char(i)
    register_key(i, num_char, number_shift_map[i])
end

-- Map common punctuation and symbols
local punctuation_map = {
    { code = 0x20, char = ' ', shift = ' ' },          -- Space
    { code = 0xBA, char = ';', shift = ':' },          -- OEM_1
    { code = 0xBB, char = '=', shift = '+' },          -- OEM_PLUS
    { code = 0xBC, char = ',', shift = '<' },          -- OEM_COMMA
    { code = 0xBD, char = '-', shift = '_' },          -- OEM_MINUS
    { code = 0xBE, char = '.', shift = '>' },          -- OEM_PERIOD
    { code = 0xBF, char = '/', shift = '?' },          -- OEM_2
    { code = 0xC0, char = '`', shift = '~' },          -- OEM_3
    { code = 0xDB, char = '[', shift = '{' },          -- OEM_4
    { code = 0xDC, char = '\\', shift = '|' },         -- OEM_5
    { code = 0xDD, char = ']', shift = '}' },          -- OEM_6
    { code = 0xDE, char = "'", shift = '"' },          -- OEM_7
}
for _, entry in ipairs(punctuation_map) do
    register_key(entry.code, entry.char, entry.shift)
end

-- Map Numpad 0-9 (VK_NUMPAD0 - VK_NUMPAD9: 0x60 - 0x69)
for i = 0x60, 0x69 do
    local num_char = string.char(i - 0x60 + 0x30)
    register_key(i, num_char, num_char)
end

-- Map Numpad operators
local numpad_operators = {
    { code = 0x6A, char = '*', shift = '*' },  -- VK_MULTIPLY
    { code = 0x6B, char = '+', shift = '+' },  -- VK_ADD
    { code = 0x6D, char = '-', shift = '-' },  -- VK_SUBTRACT
    { code = 0x6E, char = '.', shift = '.' },  -- VK_DECIMAL
    { code = 0x6F, char = '/', shift = '/' },  -- VK_DIVIDE
}
for _, entry in ipairs(numpad_operators) do
    register_key(entry.code, entry.char, entry.shift)
end

-- [Public API]
function input_module.get_vk_list()
    return VK_LIST
end

function input_module.get_key_lookup()
    return KEY_LOOKUP
end

function input_module.update_mouse_state()
    if not input then return end
    local mp = input.mouse_position()
    if mp then
        state.ui.input.mouse_pos.x = mp.x
        state.ui.input.mouse_pos.y = mp.y
    end
    local mouse_state = input.mouse(1)
    if mouse_state then
        state.ui.input.mouse_left_pressed = mouse_state.pressed
        state.ui.input.mouse_left_just_pressed = mouse_state.just_pressed
    end
end

function input_module.is_mouse_in(x, y, w, h)
    local mx, my = state.ui.input.mouse_pos.x, state.ui.input.mouse_pos.y
    return mx >= x and mx <= x + w and my >= y and my <= y + h
end

function input_module.block_game_controls()
    local natives = state.natives_cache
    local N_DISABLE_CONTROL_ACTION = natives.DISABLE_CONTROL_ACTION
    local N_DISABLE_PLAYER_FIRING = natives.DISABLE_PLAYER_FIRING
    local N_PLAYER_ID = natives.PLAYER_ID

    if not N_DISABLE_CONTROL_ACTION then return end
    if not state.ui.block_input or not state.ui.block_input_toggle then return end

    -- Lazy init merged controls on first call
    init_merged_controls()

    -- Choose control list based on text input state
    local controls, controls_len
    if state.ui.text_input_active then
        controls = MERGED_CONTROLS
        controls_len = MERGED_CONTROLS_LEN
    else
        controls = state.BLOCK_CONTROLS
        controls_len = #controls
    end

    for i = 1, controls_len do
        local ctrl = controls[i]
        if ctrl then
            N_DISABLE_CONTROL_ACTION(0, ctrl, true)
            N_DISABLE_CONTROL_ACTION(2, ctrl, true)
        end
    end

    if N_PLAYER_ID and N_DISABLE_PLAYER_FIRING then
        N_DISABLE_PLAYER_FIRING(N_PLAYER_ID(), true)
    end
end

function input_module.handle_dragging(x, y, w, h)
    local ui = state.ui
    if ui.input.mouse_left_pressed and input_module.is_mouse_in(x, y, w, h) then
        if not ui.dragging then
            ui.dragging = true
            ui.drag_offset.x = ui.input.mouse_pos.x - x
            ui.drag_offset.y = ui.input.mouse_pos.y - y
        end
    elseif not ui.input.mouse_left_pressed then
        ui.dragging = false
    end

    if ui.dragging then
        local config_sys = state.deps.config_sys
        if config_sys and config_sys.data then
            config_sys.data.base.x = ui.input.mouse_pos.x - ui.drag_offset.x
            config_sys.data.base.y = ui.input.mouse_pos.y - ui.drag_offset.y
        end
    end
end

-- [Clipboard Operations]
--- Copy text to clipboard (safe wrapper)
--- @param text string The text to copy
--- @return boolean Success status
function input_module.copy_to_clipboard(text)
    if not input or not input.set_clipboard_text then return false end
    if not text then return false end

    local ok = pcall(input.set_clipboard_text, tostring(text))
    return ok
end

--- Get text from clipboard (safe wrapper)
--- @return string|nil The clipboard text, or nil if unavailable
function input_module.get_from_clipboard()
    if not input or not input.get_clipboard_text then return nil end

    local ok, result = pcall(input.get_clipboard_text)
    if ok and result then
        return result
    end
    return nil
end

return input_module
